package com.google.forumbotty;

import java.io.IOException;
import java.util.Date;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Calendar;
import java.util.GregorianCalendar;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;

import com.google.forumbotty.dao.AdminConfigDao;
import com.google.forumbotty.dao.ForumPostDao;
import com.google.forumbotty.dao.UserNotificationDao;
import com.google.forumbotty.model.ForumPost;
import com.google.forumbotty.model.UserNotification;
import com.google.forumbotty.model.UserNotification.NotificationType;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.servlet.RequestScoped;
import com.google.wave.api.AbstractRobot;
import com.google.wave.api.Blip;
import com.google.wave.api.BlipContentRefs;
import com.google.wave.api.ElementType;
import com.google.wave.api.FormElement;
import com.google.wave.api.Line;
import com.google.wave.api.Tags;
import com.google.wave.api.Wavelet;
import com.google.wave.api.event.AbstractEvent;
import com.google.wave.api.event.BlipSubmittedEvent;
import com.google.wave.api.event.FormButtonClickedEvent;
import com.google.wave.api.event.WaveletSelfAddedEvent;

@Singleton
public class ForumBotty extends AbstractRobot {
  private static final Logger LOG = Logger.getLogger(ForumBotty.class.getName());
  private static final boolean DEBUG_MODE = false;
  
  private static String SANDBOX_DOMAIN = "wavesandbox.com";
  private static String PREVIEW_DOMAIN = "googlewave.com";

  private static String PREVIEW_RPC_URL = "http://gmodules.com/api/rpc";
  private static String SANDBOX_RPC_URL = "http://sandbox.gmodules.com/api/rpc";

  private static String OAUTH_TOKEN = null;
  private static String OAUTH_KEY = null;
  private static String OAUTH_SECRET = null;
  private static String SECURITY_TOKEN = null;
  
  private static String DIGEST_WAVE_DOMAIN = "";
  private static String DIGEST_WAVE_ID = null;
 
  
  private Injector injector = null;
  private Util util = null;
  private ForumPostDao forumPostDao = null;
  private UserNotificationDao userNotificationDao = null;
  private AdminConfigDao adminConfigDao = null;

  private String domain = PREVIEW_DOMAIN;//SANDBOX_DOMAIN;

  @Inject
  public ForumBotty(Injector injector, Util util, ForumPostDao forumPostDao,
      AdminConfigDao adminConfigDao, UserNotificationDao userNotificationDao) {
    this.injector = injector;
    this.util = util;
    this.forumPostDao = forumPostDao;
    this.userNotificationDao = userNotificationDao;
    this.adminConfigDao = adminConfigDao;

    OAUTH_TOKEN = System.getProperty("OAUTH_TOKEN");
    OAUTH_KEY = System.getProperty("OAUTH_KEY");
    OAUTH_SECRET = System.getProperty("OAUTH_SECRET");
    SECURITY_TOKEN = System.getProperty("SECURITY_TOKEN");
    DIGEST_WAVE_DOMAIN = System.getProperty("DIGEST_WAVE_DOMAIN");
    DIGEST_WAVE_ID = System.getProperty("DIGEST_WAVE_ID");

    initOauth();
  }

  public void initAdminConfig() {
    String projectId = "wave-api";

    adminConfigDao.addDefaultTag(projectId, "wave-api");
    adminConfigDao.addAutoTagRegex(projectId, "gadget", "gadget");
    adminConfigDao.addAutoTagRegex(projectId, "robot", "robot");
    adminConfigDao.addAutoTagRegex(projectId, "embed", "embed");
    adminConfigDao.addAutoTagRegex(projectId, "java", "java");
    adminConfigDao.addAutoTagRegex(projectId, "python", "python");
  }

  public String getDomain() {
    return domain;
  }

  public void initOauth() {
    setupVerificationToken(OAUTH_TOKEN, SECURITY_TOKEN);

    if (this.domain.equals(SANDBOX_DOMAIN)) {
      setupOAuth(OAUTH_KEY, OAUTH_SECRET, SANDBOX_RPC_URL);
    }
    if (this.domain.equals(PREVIEW_DOMAIN)) {
      setupOAuth(OAUTH_KEY, OAUTH_SECRET, PREVIEW_RPC_URL);
    }

    setAllowUnsignedRequests(true);
  }

  public String getRpcServerUrl() {
    if (this.domain.equals(SANDBOX_DOMAIN)) {
      return SANDBOX_RPC_URL;
    }
    if (this.domain.equals(PREVIEW_DOMAIN)) {
      return PREVIEW_RPC_URL;
    }
    return null;
  }

  public String getCurrentProjectId(AbstractEvent event) {
    return event.getBundle().getProxyingFor();
  }

  @Override
  public void onWaveletSelfAdded(WaveletSelfAddedEvent event) {
    if (isNotifyProxy(event.getBundle().getProxyingFor())) {
      processNotifyProxy(event);
      return;
    }

    // If this is from the "*-digest" proxy, skip processing.
    if (isDigestWave(event.getBundle().getProxyingFor())) {
      return;
    }     
    
    String projectId = getCurrentProjectId(event);
    if (projectId == null) {
      LOG.log(Level.SEVERE, "Missing proxy-for project id");
      return;
    }

    Wavelet wavelet = event.getWavelet();

    // Add default participants
    for (String participant : this.adminConfigDao.getAdminConfig(projectId)
        .getDefaultParticipants()) {
      wavelet.getParticipants().add(participant);
    }

    if (isWorthy(wavelet)) {
      ForumPost entry = new ForumPost(wavelet.getDomain(), wavelet);
      entry.setProjectId(projectId);

      for (String contributor : wavelet.getRootBlip().getContributors()) {
        if (isNormalUser(contributor)) {
          entry.addContributor(contributor);
        }
      }

      // Apply default and auto tags to the wave
      applyDefaultTags(wavelet, projectId);
      applyAutoTags(event.getBlip(), projectId);

      entry = forumPostDao.syncTags(projectId, entry, wavelet);
      forumPostDao.save(entry);
      updateDigestWave(entry);
    }
  }

  private boolean isNormalUser(String userId) {
    return !(userId.endsWith("appspot.com") || userId.endsWith("gwave.com"));
  }

  @Override
  public void onBlipSubmitted(BlipSubmittedEvent event) {
    // If this is from the "*-notify" proxy, skip processing.
    if (isNotifyProxy(event.getBundle().getProxyingFor())) {
      return;
    }

    // If this is from the "*-digest" proxy, skip processing.
    if (isDigestWave(event.getBundle().getProxyingFor())) {
      return;
    } 

    // If this is unworthy wavelet (empty), skip processing.
    if (!isWorthy(event.getWavelet())) {
      return;
    }

    Wavelet wavelet = event.getWavelet();

    String projectId = getCurrentProjectId(event);
    if (projectId == null) {
      LOG.log(Level.SEVERE, "Missing proxy-for project id");
      return;
    }

    ForumPost entry = forumPostDao.getForumPost(wavelet.getDomain(), 
        wavelet.getWaveId().getId());
    if (entry != null) {
      // Existing wavelet in datastore
      entry.setTitle(wavelet.getTitle());
      entry.setLastUpdated(new Date());
      entry.setBlipCount(wavelet.getBlips().size());
      // Check if post was created after we transitioned to checking whether a post was in the forum
      Calendar transitionDay = new GregorianCalendar(2010, Calendar.JULY, 18);
      if (entry.getDigested() == null && entry.getCreated().after(transitionDay.getTime())) {
    	  updateDigestWave(entry);
      }
    } else {
      // Brand new wavelet
      //entry = new ForumPost(wavelet.getDomain(), wavelet.getWaveId().getId(), wavelet.getCreator(), wavelet.getTitle(), wavelet.getBlips().size());
      entry = new ForumPost(wavelet.getDomain(), wavelet);
      entry.setProjectId(projectId);
      updateDigestWave(entry);
    }

    // Update contributor list if this is not robot or agent
    if (isNormalUser(event.getModifiedBy())) {
      entry.addContributor(event.getModifiedBy());
    }

    // Apply default and auto tags to the wave
    applyDefaultTags(wavelet, projectId);
    applyAutoTags(event.getBlip(), projectId);

    entry = forumPostDao.syncTags(projectId, entry, wavelet);
    forumPostDao.save(entry);
  }
  
  private boolean isDigestWave(String proxyingFor) {
    if (proxyingFor != null && proxyingFor.endsWith("-digest")) {
      return true;
    } else {
      return false;
    }
  }

  private void updateDigestWave(ForumPost entry) {
    Wavelet digestWavelet = null;
    
    try {
      digestWavelet = fetchWavelet(
          new WaveId(DIGEST_WAVE_DOMAIN, DIGEST_WAVE_ID), 
          new WaveletId(domain, "conv+root"), entry.getProjectId() + "-digest", 
          getRpcServerUrl());
      String entryTitle = entry.getTitle();
      
      Blip blip = digestWavelet.reply("\n");
      blip.append(entryTitle);
      int titleEnd = blip.getContent().length();
      BlipContentRefs.range(blip, 0, titleEnd).annotate("link/wave", entry.getId());
      blip.append("\n");
      BlipContentRefs.range(blip, titleEnd, titleEnd+1).clearAnnotation("link/wave");
      blip.append("By: " + entry.getCreator());
      submit(digestWavelet, getRpcServerUrl());  
      entry.setDigested();
      forumPostDao.save(entry);
    } catch (IOException e) {
      LOG.log(Level.SEVERE, e.getMessage(), e);
    }
  }
  
  private boolean isWorthy(Wavelet wavelet) {
    return !wavelet.getTitle().trim().equals("");
  }

  private void applyDefaultTags(Wavelet wavelet, String projectId) {
    Tags tags = wavelet.getTags();
    for (String tag : this.adminConfigDao.getAdminConfig(projectId).getDefaultTags()) {
      if (!tags.contains(tag)) {
        tags.add(tag);
      }
    }
  }

  private void removeDefaultTags(Wavelet wavelet, String projectId) {
    Tags tags = wavelet.getTags();
    for (String tag : this.adminConfigDao.getAdminConfig(projectId).getDefaultTags()) {
      if (tags.contains(tag)) {
        tags.remove(tag);
      }
    }
  }

  @Override
  public void onFormButtonClicked(FormButtonClickedEvent event) {
    if (isNotifyProxy(event.getBundle().getProxyingFor())) {
      Wavelet wavelet = event.getWavelet();
      String projectId = event.getBundle().getProxyingFor().replace("-notify", "");
      Blip blip = event.getBlip();
      if (event.getButtonName().equals("frequencySubmit")) {

        FormElement frequencyGroup = FormElement.class.cast(blip.first(
            ElementType.RADIO_BUTTON_GROUP).value());

        NotificationType notificationType = NotificationType.NONE;

        if (frequencyGroup.getValue().equals("none")) {
          notificationType = NotificationType.NONE;
        } else if (frequencyGroup.getValue().equals("daily")) {
          notificationType = NotificationType.DAILY;
        } else if (frequencyGroup.getValue().equals("weekly")) {
          notificationType = NotificationType.WEEKLY;
        }

        String userId = event.getModifiedBy();
        String notificationId = userId + projectId;
        UserNotification userNotification = userNotificationDao.getUserNotification(notificationId);
        if (userNotification == null) {
          userNotification = new UserNotification(userId, projectId);
        }
        userNotification.setNotificationType(notificationType);
        userNotificationDao.save(userNotification);

        wavelet
            .reply("\nYour notification frequency is now set to be " + frequencyGroup.getValue());
      }
    }
  }

  public void applyAutoTags(Blip blip, String projectId) {
    String content = blip.getContent();

    for (Entry<String, Pattern> entry : this.adminConfigDao.getAdminConfig(projectId)
        .getAutoTagRegexMap().entrySet()) {
      String tag = entry.getKey();
      Pattern pattern = entry.getValue();

      Matcher m = null;
      m = pattern.matcher(content);
      if (m.find()) {
        blip.getWavelet().getTags().add(tag);
      }
    }
  }

  private boolean isNotifyProxy(String proxyForString) {
    if (proxyForString != null && proxyForString.endsWith("-notify")) {
      return true;
    } else {
      return false;
    }
  }

  private void processNotifyProxy(WaveletSelfAddedEvent event) {
    Wavelet wavelet = event.getWavelet();

    String projectId = event.getBundle().getProxyingFor().replace("-notify", "");

    FormElement radioGroup = new FormElement(ElementType.RADIO_BUTTON_GROUP);
    String groupLabel = "frequency";
    radioGroup.setName(groupLabel);

    String userId = event.getModifiedBy();
    UserNotification userNotification = userNotificationDao.getUserNotification(userId);
    if (userNotification != null) {
      radioGroup.setValue(userNotification.getNotificationType().toString());
    }

    FormElement radioNone = new FormElement(ElementType.RADIO_BUTTON);
    radioNone.setName("none");
    radioNone.setValue(groupLabel);

    FormElement radioDaily = new FormElement(ElementType.RADIO_BUTTON);
    radioDaily.setName("daily");
    radioDaily.setValue(groupLabel);

    FormElement radioWeekly = new FormElement(ElementType.RADIO_BUTTON);
    radioWeekly.setName("weekly");
    radioWeekly.setValue(groupLabel);

    wavelet.setTitle("How often would you like to receive digest from " + projectId + "?");
    Blip rootBlip = wavelet.getRootBlip();

    rootBlip.append(new Line());

    rootBlip.append(radioGroup);

    rootBlip.append(radioNone);
    rootBlip.append("none");
    rootBlip.append(new Line());

    rootBlip.append(radioDaily);
    rootBlip.append("daily");
    rootBlip.append(new Line());

    rootBlip.append(radioWeekly);
    rootBlip.append("weekly");
    rootBlip.append(new Line());
    rootBlip.append(new Line());

    FormElement frequencySubmit = new FormElement(ElementType.BUTTON);
    frequencySubmit.setName("frequencySubmit");
    frequencySubmit.setValue("submit");

    rootBlip.append(frequencySubmit);
  }

  private String getServerName() {
    ServletHelper servletHelper = injector.getInstance(ServletHelper.class);
    return servletHelper.getRequest().getServerName();
  }

  public String getRobotAvatarUrl() {
    return "http://" + getServerName() + "/images/forumbotty_thumb.png";
  }

  public String getRobotProfilePageUrl() {
    return "http://" + getServerName() + "/_wave/robot/profile";
  }

  @Override
  public String getRobotName() {
    return getServerName().replace(".appspot.com", "");
  }

  @RequestScoped
  private static class ServletHelper {
    private HttpServletRequest request = null;
    private HttpServletResponse response = null;

    @Inject
    public ServletHelper(HttpServletRequest request, HttpServletResponse response) {
      this.request = request;
      this.response = response;
    }

    public HttpServletRequest getRequest() {
      return this.request;
    }

    public HttpServletResponse getResponse() {
      return response;
    }
  }
}
